@@ -1,5 +1,6 @@ |
||
| 1 | 1 |
# Changes |
| 2 | 2 |
|
| 3 |
+* 0.2 (Nov 6, 2013) - PeakDetectorAgent now uses `window_duration_in_days` and `min_peak_spacing_in_days`. Additionally, peaks trigger when the time series rises over the standard deviation multiple, not after it starts to fall. |
|
| 3 | 4 |
* June 29, 2013 - Removed rails\_admin because it was causing deployment issues. Better to have people install their favorite admin tool if they want one. |
| 4 | 5 |
* June, 2013 - A number of new agents have been contributed, including interfaces to Weibo, Twitter, and Twilio, as well as Agents for translation, sentiment analysis, and for posting and receiving webhooks. |
| 5 | 6 |
* March 24, 2013 (0.1) - Refactored loading of Agents for `check` and `receive` to use ids instead of full objects. This should fix the too-large delayed_job issues. Added `system_timer` and `fastercsv` to the Gemfile for the Ruby 1.8 platform. |
@@ -1 +1 @@ |
||
| 1 |
-0.1 |
|
| 1 |
+0.2 |
@@ -11,9 +11,9 @@ module Agents |
||
| 11 | 11 |
|
| 12 | 12 |
Set `expected_receive_period_in_days` to the maximum amount of time that you'd expect to pass between Events being received by this Agent. |
| 13 | 13 |
|
| 14 |
- You may set `window_duration` to change the default memory window length of two weeks, |
|
| 15 |
- `peak_spacing` to change the default minimum peak spacing of two days, and |
|
| 16 |
- `std_multiple` to change the default standard deviation threshold multiple of 3. |
|
| 14 |
+ You may set `window_duration_in_days` to change the default memory window length of `14` days, |
|
| 15 |
+ `min_peak_spacing_in_days` to change the default minimum peak spacing of `2` days (peaks closer together will be ignored), and |
|
| 16 |
+ `std_multiple` to change the default standard deviation threshold multiple of `3`. |
|
| 17 | 17 |
MD |
| 18 | 18 |
|
| 19 | 19 |
event_description <<-MD |
@@ -61,27 +61,22 @@ module Agents |
||
| 61 | 61 |
memory[:peaks][group] ||= [] |
| 62 | 62 |
|
| 63 | 63 |
if memory[:data][group].length > 4 && (memory[:peaks][group].empty? || memory[:peaks][group].last < event.created_at.to_i - peak_spacing) |
| 64 |
- average_value, standard_deviation = stats_for(group, :skip_last => 2) |
|
| 65 |
- newest_value = memory[:data][group][-1].first.to_f |
|
| 66 |
- second_newest_value, second_newest_time = memory[:data][group][-2].map(&:to_f) |
|
| 67 |
- |
|
| 68 |
- #pp({:newest_value => newest_value,
|
|
| 69 |
- # :second_newest_value => second_newest_value, |
|
| 70 |
- # :average_value => average_value, |
|
| 71 |
- # :standard_deviation => standard_deviation, |
|
| 72 |
- # :threshold => average_value + std_multiple * standard_deviation }) |
|
| 73 |
- |
|
| 74 |
- if newest_value < second_newest_value && second_newest_value > average_value + std_multiple * standard_deviation |
|
| 75 |
- memory[:peaks][group] << second_newest_time |
|
| 76 |
- memory[:peaks][group].reject! { |p| p <= second_newest_time - window_duration }
|
|
| 77 |
- create_event :payload => {:message => options[:message], :peak => second_newest_value, :peak_time => second_newest_time, :grouped_by => group.to_s}
|
|
| 64 |
+ average_value, standard_deviation = stats_for(group, :skip_last => 1) |
|
| 65 |
+ newest_value, newest_time = memory[:data][group][-1].map(&:to_f) |
|
| 66 |
+ |
|
| 67 |
+ #p [newest_value, average_value, average_value + std_multiple * standard_deviation, standard_deviation] |
|
| 68 |
+ |
|
| 69 |
+ if newest_value > average_value + std_multiple * standard_deviation |
|
| 70 |
+ memory[:peaks][group] << newest_time |
|
| 71 |
+ memory[:peaks][group].reject! { |p| p <= newest_time - window_duration }
|
|
| 72 |
+ create_event :payload => {:message => options[:message], :peak => newest_value, :peak_time => newest_time, :grouped_by => group.to_s}
|
|
| 78 | 73 |
end |
| 79 | 74 |
end |
| 80 | 75 |
end |
| 81 | 76 |
|
| 82 | 77 |
def stats_for(group, options = {})
|
| 83 | 78 |
data = memory[:data][group].map { |d| d.first.to_f }
|
| 84 |
- data = data[0...(memory[:data][group].length - (options[:skip_last] || 0))] |
|
| 79 |
+ data = data[0...(data.length - (options[:skip_last] || 0))] |
|
| 85 | 80 |
length = data.length.to_f |
| 86 | 81 |
mean = 0 |
| 87 | 82 |
mean_variance = 0 |
@@ -99,15 +94,23 @@ module Agents |
||
| 99 | 94 |
end |
| 100 | 95 |
|
| 101 | 96 |
def window_duration |
| 102 |
- (options[:window_duration].present? && options[:window_duration].to_i) || 2.weeks |
|
| 97 |
+ if options[:window_duration].present? # The older option |
|
| 98 |
+ options[:window_duration].to_i |
|
| 99 |
+ else |
|
| 100 |
+ (options[:window_duration_in_days] || 14).to_f.days |
|
| 101 |
+ end |
|
| 103 | 102 |
end |
| 104 | 103 |
|
| 105 | 104 |
def std_multiple |
| 106 |
- (options[:std_multiple].present? && options[:std_multiple].to_i) || 3 |
|
| 105 |
+ (options[:std_multiple] || 3).to_f |
|
| 107 | 106 |
end |
| 108 | 107 |
|
| 109 | 108 |
def peak_spacing |
| 110 |
- (options[:peak_spacing].present? && options[:peak_spacing].to_i) || 2.days |
|
| 109 |
+ if options[:peak_spacing].present? # The older option |
|
| 110 |
+ options[:peak_spacing].to_i |
|
| 111 |
+ else |
|
| 112 |
+ (options[:min_peak_spacing_in_days] || 2).to_f.days |
|
| 113 |
+ end |
|
| 111 | 114 |
end |
| 112 | 115 |
|
| 113 | 116 |
def group_for(event) |
@@ -36,7 +36,7 @@ describe Agents::PeakDetectorAgent do |
||
| 36 | 36 |
end |
| 37 | 37 |
|
| 38 | 38 |
it "keeps a rolling window of data" do |
| 39 |
- @agent.options[:window_duration] = 5.hours |
|
| 39 |
+ @agent.options[:window_duration_in_days] = 5/24.0 |
|
| 40 | 40 |
@agent.receive build_events(:keys => [:count], |
| 41 | 41 |
:values => [1, 2, 3, 4, 5, 6, 7, 8].map {|i| [i]},
|
| 42 | 42 |
:pattern => { :filter => "something" })
|
@@ -47,14 +47,14 @@ describe Agents::PeakDetectorAgent do |
||
| 47 | 47 |
build_events(:keys => [:count], |
| 48 | 48 |
:values => [5, 6, |
| 49 | 49 |
4, 5, |
| 50 |
- 8, 11, |
|
| 50 |
+ 4, 5, |
|
| 51 | 51 |
15, 11, # peak |
| 52 |
- 8, 5, |
|
| 52 |
+ 8, 50, # ignored because it's too close to the first peak |
|
| 53 | 53 |
4, 5].map {|i| [i]},
|
| 54 | 54 |
:pattern => { :filter => "something" }).each.with_index do |event, index|
|
| 55 | 55 |
lambda {
|
| 56 | 56 |
@agent.receive([event]) |
| 57 |
- }.should change { @agent.events.count }.by( index == 7 ? 1 : 0 )
|
|
| 57 |
+ }.should change { @agent.events.count }.by( index == 6 ? 1 : 0 )
|
|
| 58 | 58 |
end |
| 59 | 59 |
|
| 60 | 60 |
@agent.events.last.payload[:peak].should == 15.0 |
@@ -62,10 +62,9 @@ describe Agents::PeakDetectorAgent do |
||
| 62 | 62 |
end |
| 63 | 63 |
|
| 64 | 64 |
it "keeps a rolling window of peaks" do |
| 65 |
- @agent.options[:window_duration] = 5.hours |
|
| 66 |
- @agent.options[:peak_spacing] = 1.hour |
|
| 65 |
+ @agent.options[:min_peak_spacing_in_days] = 1/24.0 |
|
| 67 | 66 |
@agent.receive build_events(:keys => [:count], |
| 68 |
- :values => [1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 10, 1, 1, 1, 10, 1].map {|i| [i]},
|
|
| 67 |
+ :values => [1, 1, 1, 1, 1, 1, 10, 1, 1, 1, 1, 1, 1, 1, 10, 1].map {|i| [i]},
|
|
| 69 | 68 |
:pattern => { :filter => "something" })
|
| 70 | 69 |
@agent.memory[:peaks][:something].length.should == 2 |
| 71 | 70 |
end |